I thought I should write little bit about the progress on the r7rs-pffi. If you dont know what that is here is a link but shortly, it is my attempt to make an foreign function library that works on multiple R7RS Scheme implementations. So I do not have to write libraries multiple times.
It started also as a way to learn about C. I dit not want to write a C project as the language can be quite strenuous to the coder by way of being hard to debug and having manual memory management, and all that. That said C is big part of thins still and having access to C libraries from your scripting language is definitely desirable.
TL;DR
The library is still quite messy and in alpha, but definitely pass the point of "Is it possible?".
Dynamic FFI
Ypsilon
I added support for Ypsilon, the C ffi was a joy to work with and I was able to add support in just a half day. But of course there is still one thing. The pffi-define and pffi-define callback functions are implemented with define-macro, as the Ypsilon equivalents are macros too they expect input to be for example just int and not 'int. This means that we will need to somehow import the (ypsilon c-ffi) library into the users code. On Chicken this is done using (pffi-init):
(cond-expand
((or chicken-5 chicken-6)
(define-syntax pffi-init
(er-macro-transformer
(lambda (expr rename compare)
'(import (chicken foreign)
(chicken memory))
#t))))
(else
(define pffi-init(lambda () #t))))
But the same trick does not work for Ypsilon, it complains about the import. If you have suggestions feel free to comment on the issue.
Note: While writing this I noticed that the trick does not seem to work on Chicken anymore either. Issue.
Compiling library C code
Chibi and Gauche
Chibi and Gauche both do not have libffi based "dynamic" ffi built into them. So for the first time some implementations needed C code to add support. This is C code that is compiled on the installation of the pffi library, not when user uses the library made with pffi.
For this libffi is used as it is the best(and only?) ffi library in town. As with other thing I've run into with C it seemed to be scary and complicated at first but from that one tends to go to "Okay this is quite simple." and then to "Okay this looks simple but there be dragons".
I'm assuming support for other implementations that need C code written, atleast tr7 and Skint, will also be quite straighforward to implement using libffi. The only question mark is passing the structs by value but when feature is added with one implementation that experience usually transfers to other implementations too.
About Chibi and Gauche. If youre thinking about which one to use then my two cents are that Chibi C interface is straightforward to use and does not require much boilerplate. Gauche on the other hand needed more boilerplate and even partial duplication, in the form of C header in addition to source, but the C side has a lot of features and propably will allow much more flexibility than Chibi in the long run.
Example of Chibi modules stub code:
(c-declare "void* pointer_null() { return NULL; }")
(define-c (pointer void*) (pointer-null pointer_null) ())
Example of Gauche modules C code:
ScmModule* module = NULL;
ScmObj pointer_null() {
ScmClass* class = Scm_MakeForeignPointerClass(module, "<pffi-pointer>", print_pointer, NULL, 0);
ScmObj pointer = Scm_MakeForeignPointer(class, NULL);
return pointer;
}
Compiling Scheme to C
Chicken
Big improvement, that actually happened a while ago, is adding support for Scheme to C compilers. This is implemented on the interface side in pffi-shared-object-auto-load (which I'm considering renaming to just pffi-load) by adding headers argument. Here is example how you would load SDL2:
(define clibd-sdl2
(pffi-shared-object-auto-load (list "SDL2/SDL.h")
"SDL2-2.0"))
On implementations with "dynamic" FFI the library uses the "SDL2-2.0" value to load the shared library. On compiler implementations the header "SDL2/SDL.h" is included. Here is example of chickens include code:
(define-syntax pffi-shared-object-load
(er-macro-transformer
(lambda (expr rename compare)
(let* ((headers (cdr (car (cdr expr)))))
`(begin
,@ (map
(lambda (header)
`(foreign-declare ,(string-append "#include <" header ">")))
headers))))))
You will of course need to add the -lSDL2 and such flags for Chicken, but from portable code point of view the same code runs on both kinds of implementations. Of course if you run it with just an interpreter and dont compile it, then the header can be wrong or other way around. But of course you test things, right? :>
SDL2 testing
I have a small SDL2 test that I can run to see if implementation really works, thought the test suite is in better condition regarding that too, it opens a window, draws some pictures and takes input. Here is a picture of it running on Chicken:
Here is the same code running on Sagittarius:
Thrilling, isn't it! :D The reason the second picture has black block and the picture of bottles is in different position is that those follow mouse and keyboard input.
The pictures themself are not very exciting of course , but that it is running on Chicken and Sagittarius is. I will not add pictures for other implementations but as of today the same code runs on Chibi, Chicken, Gauche, Guile, Kawa, Racket, Sagittarius and Ypsilon.
Whats next?
Support for another compilers
Gambit
It would be nice to have support for Gambit, to make sure the interface works for another compiler. But I dont expect it to work much differently from Chicken. I've dabbled with Gambit support for a little bit and it atleast seems similar.
Cyclone
Cyclone is another compiler I have my eyes on, and the manual says (cyclone foreign) is based on (chicken foreign) so I'm not expecting massive suprises there.
Passing structs by value
Propably the biggest thing left is to design the interface for how to pass structs by value. It is important that this works the same, from the user point of view, on all implementations. So having "dynamic" ffi, C code using libffi written by myself, and Scheme to C compilers adds to the complexity.
The struct handling code is currently portable and uses pffi-pointer-allocate and such to allocate, read and modify structures. pffi-make-struct takes a name as argument, so that could be used on compilers to cast the pointer to right type. Chicken 5 manual says it does not support passing structs by value, but version 6 does. The weirdest thing is that for a moment deref from Sagittarius, and similar seemed to work and pass structs by value. SDL2 is good test for that since the text drawing takes color as struct argument. But they stopped working for some reason. Maybe switching from Guix to Debian stable means that the libffi version is older or something like that. I need to investigate and learn more about passing structs by values anyhow.
Conclusion
Everything rolling on nicely. And I should propably write more since this post seems little bit incoherent.